/****************************************************************************
 * Copyright (C) 2009-2010 SciTouch LLC
 *
 * This file is part of Indigo toolkit.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include "molecule/molecule.h"
#include "reaction/reaction.h"
#include "base_cpp/scanner.h"
#include "base_cpp/output.h"
#include "base_cpp/obj.h"
#include "base_cpp/obj_array.h"
#include "graph/graph_highlighting.h"
#include "render2d/render_params.h"
#include "dingo_c.h"

#ifdef _WIN32
   #pragma warning (disable:4800)
#endif

struct OptionString
{
   const char* name;
   int (*func) (const char* value);
};     

struct OptionSize
{
   const char* name;
   int (*func) (int w, int h);
};     

struct OptionBool
{
   const char* name;
   int (*func) (int enabled);
};     

struct OptionFloat
{
   const char* name;
   int (*func) (float value);
};     

struct OptionColor
{
   const char* name;
   int (*func) (float r, float g, float b);
};     

struct OptionInt
{
   const char* name;
   int (*func) (int value);
};     

CEXPORT int dingoSetQueryMode(int enabled);

// string
OptionString optionString[] = {
   {"input-format", dingoSetInputFormat},
   {"output-format", dingoSetOutputFormat},
   {"output-file", dingoSetOutputFile},
   {"implicit-hydrogen-mode", dingoSetImplicitHydrogenMode},
   {"aromatization", dingoSetAromatization},
   {"label-mode", dingoSetLabelMode},
   {"comment", dingoSetComment},
   {"comment-position", dingoSetCommentPosition},
   {"comment-alignment", dingoSetCommentAlignment}};

// size
OptionSize optionSize[] = {
   {"image-size", dingoSetImageSize}}; // PCHECK(parseSize(value, w, h));

// float
OptionFloat optionFloat[] = {
   {"bond-length", dingoSetBondLength}, // PCHECK(parseFloat(value, v));
   {"margin-factor", dingoSetMarginFactor},           
   {"relative-thickness", dingoSetRelativeThickness},
   {"comment-font-size", dingoSetCommentFontSize}};

// color
OptionColor optionColor[] = {
   {"background-color", dingoSetBackgroundColor}, /*PCHECK(parseColor(value, r, g, b));*/
   {"base-color", dingoSetBaseColor},
   {"highlight-color", dingoSetHighlightColor},
   {"comment-color", dingoSetCommentColor},
   {"aam-color", dingoSetAAMColor}};

// bool
OptionBool optionBool[] = {
   {"coloring", dingoSetColoring}, // PCHECK(parseEnabled(value, e));
   {"valences-visible", dingoSetValencesVisible},
   {"atom-ids-visible", dingoSetAtomIdsVisible},
   {"bond-ids-visible", dingoSetBondIdsVisible},
   {"stereo-old-style", dingoSetStereoOldStyle},
   {"highlight-thickness-enabled", dingoSetHighlightThicknessEnabled},
   {"highlight-color-enabled", dingoSetHighlightColorEnabled},
   {"center-double-bond-when-stereo-adjacent", dingoSetCenterDoubleBondWhenStereoAdjacent},
   {"query", dingoSetQueryMode}};

struct ErrorDesc
{
   Array<char> text;
};

TL_DECL(ErrorDesc, _errordesc);
TL_DECL(RenderParams, _params);
TL_DECL(Array<char>, _outbuf);
TL_DECL(Obj<FileOutput>, _outfileobj);
TL_DECL(Obj<ArrayOutput>, _outbufobj);

TL_DECL(Obj<FileOutput>, _log);

void disposeOutput ()
{
   TL_GET(Obj<FileOutput>, _outfileobj);
   TL_GET(Obj<ArrayOutput>, _outbufobj);
   _outbufobj.free();
   _outfileobj.free();
}

#define GETLOG TL_GET(Obj<FileOutput>, _log)

#define CHECKLOG if (_log.get() != 0)

#define DINGO_BEGIN try { GETLOG

#define DINGO_PRINTERROR(type, error) {TL_GET(Obj<FileOutput>, _log);    \
      if (_log.get() != 0) { _log->printf("%s: %s\n", type, error); _log->flush(); } \
      disposeOutput();                                        \
      TL_GET(ErrorDesc, _errordesc);                     \
      bprintf(_errordesc.text, "%s: %s", type, error);}

#define CHECKRGB(r, g, b) \
if (__min3(r, g, b) < 0 || __max3(r, g, b) > 1.0 + 1e-6)                                     \
{                                                                                            \
   DINGO_PRINTERROR("invalid parameter", "Some of the components are out of range [0..1]");  \
   return 0;                                                                                 \
}

#define DINGO_END } catch (Exception &ex) {        \
      DINGO_PRINTERROR("exception", ex.message()); \
      return 0;                                    \
   }                                               \
   return 1

DEF_ERROR("dingo C interface");

#define DINGO_VERSION_ENCODE(major, minor) ((major) << 16 | minor)

#define DINGO_VERSION DINGO_VERSION_ENCODE(DINGO_VERSION_MAJOR, DINGO_VERSION_MINOR)

#define DINGO_VERSION_STRINGIZE_(major, minor, rev) \
   #major"."#minor"."#rev

#define DINGO_VERSION_STRINGIZE(major, minor, rev) \
   DINGO_VERSION_STRINGIZE_(major, minor, rev)

#define DINGO_VERSION_STRING DINGO_VERSION_STRINGIZE( \
   DINGO_VERSION_MAJOR, \
   DINGO_VERSION_MINOR, \
   DINGO_VERSION_REVISION)

CEXPORT int dingoVersionCode (void)
{
   return DINGO_VERSION_ENCODE(DINGO_VERSION_MAJOR, DINGO_VERSION_MINOR);
}

CEXPORT const char* dingoVersionRevisionString (void)
{
   return DINGO_VERSION_STRING;
}

void dingoLogPrintf (Obj<FileOutput> &log, const char *format, ...)
{
   va_list args;
   if (log.get() == 0)
      return;

   log->printf("[thread %u] ", osGetThreadID());
   va_start(args, format);
   log->vprintf(format, args);
   va_end(args);
   log->flush();
}

static const char* enabledToString( int enabled )
{
   return enabled ? "enabled" : "disabled";
}

CEXPORT qword dingoAllocSessionId ()
{
   return TL_ALLOC_SESSION_ID();
}

CEXPORT void dingoSetSessionId (qword id)
{
   TL_SET_SESSION_ID(id);
}

CEXPORT qword dingoGetSessionId ()
{
   return TL_GET_SESSION_ID();
}

CEXPORT void dingoReleaseSessionId (qword id)
{
   TL_RELEASE_SESSION_ID(id);
}

CEXPORT void dingoBeginLog (const char* path)
{
   TL_GET(Obj<FileOutput>, _log);
   _log.free();
   _log.create(path);
}

CEXPORT void dingoEndLog ()
{
   TL_GET(Obj<FileOutput>, _log);
   _log.free();
}

CEXPORT const char* dingoGetLastError ()
{
   TL_GET(ErrorDesc, _errordesc);
   return _errordesc.text.ptr();
}

CEXPORT int dingoSetLoadHighlighting (int enabled)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   if (enabled)
      dingoLogPrintf(_log, "load highlighting enabled\n");
   else
      dingoLogPrintf(_log, "load highlighting disabled\n");
   _params.loadHighlighting = (enabled != 0);
   DINGO_END;
}

CEXPORT int dingoLoadMolFromFile (const char *filename)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   dingoLogPrintf(_log, "load molecule from file: %s\n", filename);
   FileScanner scanner(filename);
   RenderParamInterface::loadMol(_params, scanner);
   DINGO_END;
}

CEXPORT int dingoLoadMolFromBuffer (const byte *buf, int buf_size)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   dingoLogPrintf(_log, "load molecule from buffer\n\tsize: %i\n", buf_size);
   BufferScanner scanner(buf, buf_size);
   RenderParamInterface::loadMol(_params, scanner);
   DINGO_END;
}

CEXPORT int dingoLoadMolFromString (const char *str)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   dingoLogPrintf(_log, "load molecule from string\ndata: %s\n", str);
   BufferScanner scanner(str);
   RenderParamInterface::loadMol(_params, scanner);
   DINGO_END;
}


CEXPORT int dingoLoadReactionFromFile (const char *filename)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   dingoLogPrintf(_log, "load reaction from file: %s\n", filename);
   FileScanner scanner(filename);
   RenderParamInterface::loadRxn(_params, scanner);
   DINGO_END;
}

CEXPORT int dingoLoadReactionFromBuffer (const byte *buf, int buf_size)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   dingoLogPrintf(_log, "load reaction from buffer\n\tsize: %i\n", buf_size);
   BufferScanner scanner(buf, buf_size);
   RenderParamInterface::loadRxn(_params, scanner);
   DINGO_END;
}

CEXPORT int dingoLoadReactionFromString (const char *str)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   dingoLogPrintf(_log, "load reaction from string\ndata: %s\n", str);
   BufferScanner scanner(str);
   RenderParamInterface::loadRxn(_params, scanner);
   DINGO_END;
}

CEXPORT int dingoMoleculeIsEmpty ()
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   return _params.mol.vertexCount() > 0 ? 0 : 1;
   DINGO_END;
}

CEXPORT int dingoReactionIsEmpty ()
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   return (_params.rxn.reactantsCount() + _params.rxn.productsCount() > 0) ? 0 : 1;
   DINGO_END;
}

int parseEnabled (const char *str, int &val)
{
   if (strcasecmp(str, "true") == 0)
      val = 1;
   else if (strcasecmp(str, "false") == 0)
      val = 0;
   else if (sscanf(str, "%i", &val) != 1)
      return -1;
   return 1;
}

int parseFloat (const char *str, float& val)
{
   if (sscanf(str, "%f", &val) != 1)
      return -1;
   return 1;
}

int parseColor (const char *str, float& r, float& g, float& b)
{
   if (sscanf(str, "%f,%f,%f", &r, &g, &b) != 3)
      return -1;
   return 1;
}

int parseSize (const char *str, int& w, int& h)
{
   if (sscanf(str, "%d,%d", &w, &h) != 2)
      return -1;
   return 1;
}

#define PCHECK(stmt) if (stmt < 0) throw Error("Error parsing: %s", value)

CEXPORT int dingoSetOptions (const char *options)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set options: %s\n", options);
   TL_GET(RenderParams, _params);
   BufferScanner scn(options);
   Array<char> propName, propValue;
   char c;
   while (!scn.isEOF()) {
      propName.clear();
      propValue.clear();
      scn.skipSpace();
      do {
         c = scn.readChar();
         if (!isalpha(c) && c != '-')
            break;
         propName.push(c);
      } while (!scn.isEOF());
      propName.push(0);

      while (!scn.isEOF() && c != '=') {
         if (!isspace(c))
            throw Error("Property name should be followed by an equality sign");
         c = scn.readChar();
      }
      if (scn.isEOF())
         throw Error("Unexpected end of input");

      scn.skipSpace();
      while (!scn.isEOF()) {
         c = scn.readChar();
         if (c == ';' || c == '\n' || c == '\r')
            break;
         propValue.push(c);
      }
      while (isspace(propValue[propValue.size() - 1]))
         propValue.pop();
      propValue.push(0);
      
      if (dingoSetOption(propName.ptr(), propValue.ptr()) == 0)
         throw Error("Error parsing property name=%s, value=%s", propName.ptr(), propValue.ptr());
   }
   DINGO_END;
}

CEXPORT int dingoSetOptionSize (const char *name, int w, int h)
{
   bool done = false;
   DINGO_BEGIN;
   for (int i = 0; i < NELEM(optionSize) && !done; ++i) {
      if (strcasecmp(name, optionSize[i].name) == 0) {
         optionSize[i].func(w, h);
         done = true;
      }
   }
   if (!done)
      throw Error("Property name \"%s\" unknown.", name);
   DINGO_END;
}

CEXPORT int dingoSetOptionEnabled (const char *name, int enabled)
{
   bool done = false;
   DINGO_BEGIN;
   for (int i = 0; i < NELEM(optionBool) && !done; ++i) {
      if (strcasecmp(name, optionBool[i].name) == 0) {
         optionBool[i].func(enabled);
         done = true;
      }
   }
   if (!done)
      throw Error("Property name \"%s\" unknown.", name);
   DINGO_END;
}

CEXPORT int dingoSetOptionFloat (const char *name, float value)
{
   bool done = false;
   DINGO_BEGIN;
   for (int i = 0; i < NELEM(optionFloat) && !done; ++i) {
      if (strcasecmp(name, optionFloat[i].name) == 0) {
         optionFloat[i].func(value);
         done = true;
      }
   }
   if (!done)
      throw Error("Property name \"%s\" unknown.", name);
   DINGO_END;
}

CEXPORT int dingoSetOptionColor (const char *name, float r, float g, float b)
{
   bool done = false;
   DINGO_BEGIN;
   for (int i = 0; i < NELEM(optionColor) && !done; ++i) {
      if (strcasecmp(name, optionColor[i].name) == 0) {
         optionColor[i].func(r, g, b);
         done = true;
      }
   }
   if (!done)
      throw Error("Property name \"%s\" unknown.", name);
   DINGO_END;
}

CEXPORT int dingoSetOption (const char *name, const char *value)
{
   float r, g, b, v;
   int w, h, e, i;
   bool done = false;
   DINGO_BEGIN;
   for (i = 0; i < NELEM(optionString) && !done; ++i) {
      if (strcasecmp(name, optionString[i].name) == 0) {
         optionString[i].func(value);
         done = true;
      }
   }

   for (i = 0; i < NELEM(optionSize) && !done; ++i) {
      if (strcasecmp(name, optionSize[i].name) == 0) {
         PCHECK(parseSize(value, w, h));
         optionSize[i].func(w, h);
         done = true;
      }
   }

   for (i = 0; i < NELEM(optionFloat) && !done; ++i) {
      if (strcasecmp(name, optionFloat[i].name) == 0) {
         PCHECK(parseFloat(value, v));
         optionFloat[i].func(v);
         done = true;
      }
   }

   for (i = 0; i < NELEM(optionColor) && !done; ++i) {
      if (strcasecmp(name, optionColor[i].name) == 0) {
         PCHECK(parseColor(value, r, g, b));
         optionColor[i].func(r, g, b);
         done = true;
      }
   }

   for (i = 0; i < NELEM(optionBool) && !done; ++i) {
      if (strcasecmp(name, optionBool[i].name) == 0) {
         PCHECK(parseEnabled(value, e));
         optionBool[i].func(e);
         done = true;
      }
   }

   if (!done)
      throw Error("Property name \"%s\" unknown.", name);
   DINGO_END;
}

CEXPORT int dingoSetOutputFormat (const char *format)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set output format: %s\n", format);
   TL_GET(RenderParams, _params);
   if (strcasecmp(format, "pdf") == 0)
      _params.mode = MODE_PDF;
   else if (strcasecmp(format, "png") == 0)
      _params.mode = MODE_PNG;
   else if (strcasecmp(format, "svg") == 0)
      _params.mode = MODE_SVG;
   else if (strcasecmp(format, "wmf") == 0)
      _params.mode = MODE_WMF;
   else if (strcasecmp(format, "mol") == 0)
      _params.mode = MODE_LAYOUT_MOL;
   else if (strcasecmp(format, "rxn") == 0)
      _params.mode = MODE_LAYOUT_RXN;
   else if (strcasecmp(format, "hdc") == 0)
      throw Error("use dingoSetOutputHDC() instead");
   else
      throw Error("format unrecognized: \"%s\"", format);
   DINGO_END;
}

CEXPORT int dingoSetInputFormat (const char *format)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set input format: %s\n", format);
   TL_GET(RenderParams, _params);
   if (strcasecmp(format, "smiles") == 0)
      _params.inputFormat = INPUT_FORMAT_SMILES;
   else if (strcasecmp(format, "rsmiles") == 0)
      _params.inputFormat = INPUT_FORMAT_REACTION_SMILES;
   else if (strcasecmp(format, "molfile") == 0)
      _params.inputFormat = INPUT_FORMAT_MOLFILE;
   else if (strcasecmp(format, "rxnfile") == 0)
      _params.inputFormat = INPUT_FORMAT_RXNFILE;
   else
      throw Error("input format unrecognized: \"%s\"", format);
   DINGO_END;
}

CEXPORT int dingoSetImageSize (int width, int height)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set image size: %ix%i\n", width, height);
   TL_GET(RenderParams, _params);
   _params.cnvOpt.width = width;
   _params.cnvOpt.height = height;
   DINGO_END;
}

CEXPORT int dingoSetHDCOffset (int x, int y)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set hdc offset: %ix%i\n", x, y);
   TL_GET(RenderParams, _params);
   _params.cnvOpt.xOffset = x;
   _params.cnvOpt.yOffset = y;
   DINGO_END;
}

CEXPORT int dingoSetBondLength (float length)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set bond length: %f\n", length);
   TL_GET(RenderParams, _params);
   _params.cnvOpt.bondLength = length;
   DINGO_END;
}

CEXPORT int dingoSetMarginFactor (float factor)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set margin factor: %f\n", factor);
   TL_GET(RenderParams, _params);
   _params.cnvOpt.marginFactor = factor;
   DINGO_END;
}

CEXPORT int dingoSetOutputFile (const char *filename)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set output file: %s\n", filename);
   TL_GET(RenderParams, _params);
   bprintf(_params.outfile, "%s", filename);
   DINGO_END;
}

CEXPORT int dingoSetOutputHDC (PVOID hdc)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set output hdc: %p\n", hdc);
   TL_GET(RenderParams, _params);
   _params.hdc = hdc;
   _params.mode = MODE_HDC;
   DINGO_END;
}

CEXPORT int dingoSetOutputHDCPrinting (PVOID hdc)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "set output printing hdc: %p\n", hdc);
   TL_GET(RenderParams, _params);
   _params.hdc = hdc;
   _params.mode = MODE_PRN;
   DINGO_END;
}

CEXPORT int dingoSetRelativeThickness (float rt)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   if (rt <= 0.0f)
      throw Error("relative thickness must be positive");
   _params.relativeThickness = rt;
   DINGO_END;
}

CEXPORT int dingoSetBackgroundColor (float r, float g, float b)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   CHECKLOG {
      _log->printf("set background color: (%.2lf, %.2lf, %.2lf)\n", r, g, b);
   }
   _params.backgroundColor.set((float)r, (float)g, (float)b);
   DINGO_END;
}

CEXPORT int dingoSetBaseColor (float r, float g, float b)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   CHECKLOG {
      _log->printf("set base color: (%.2lf, %.2lf, %.2lf)\n", r, g, b);
   }
   _params.baseColor.set((float)r, (float)g, (float)b);
   DINGO_END;
}

CEXPORT int dingoSetImplicitHydrogenMode (const char* mode)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   if (strcasecmp(mode, "none") == 0)
      _params.rOpt.implHMode = IHM_NONE;
   else if (strcasecmp(mode, "terminal") == 0)
      _params.rOpt.implHMode = IHM_TERMINAL;
   else if (strcasecmp(mode, "hetero") == 0)
      _params.rOpt.implHMode = IHM_HETERO;
   else if (strcasecmp(mode, "terminalhetero") == 0)
      _params.rOpt.implHMode = IHM_TERMINAL_HETERO;
   else if (strcasecmp(mode, "all") == 0)
      _params.rOpt.implHMode = IHM_ALL;
   else
      throw Error("implicit hydrogen mode unrecognized: \"%s\"", mode);
   dingoLogPrintf(_log, "implicit hydrogen mode set to \"%s\"\n", mode);
   DINGO_END;
}

CEXPORT int dingoSetColoring (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "coloring %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.rOpt.atomColoring = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetValencesVisible (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "valences %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.rOpt.showValences = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetAtomIdsVisible (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "atom ids %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.rOpt.showAtomIds = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetBondIdsVisible (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "bond ids %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.rOpt.showBondIds = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetHighlightThicknessEnabled (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "highlighting by thickness %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.hlOpt.highlightThicknessEnable = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetHighlightColorEnabled (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "highlighting by color %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.hlOpt.highlightColorEnable = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetHighlightColor (float r, float g, float b)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "highlighting color set to (%f, %f, %f)\n", r, g, b);
   TL_GET(RenderParams, _params);
   CHECKRGB(r, g, b);
   _params.hlOpt.highlightColor.set(r, g, b);
   DINGO_END;
}

CEXPORT int dingoSetStereoOldStyle (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "stereo old style %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.rOpt.useOldStereoNotation = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetAromatization (const char* arom)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   if (strcasecmp(arom, "none") == 0)
      _params.aromatization = 0;
   else if (strcasecmp(arom, "arom") == 0 || strcasecmp(arom, "aromatize") == 0)
      _params.aromatization = 1;
   else if (strcasecmp(arom, "dear") == 0 || strcasecmp(arom, "dearom") == 0 || strcasecmp(arom, "dearomatize") == 0)
      _params.aromatization = -1;
   else
      throw Error("aromatization mode unrecognized: \"%s\"", arom);
   dingoLogPrintf(_log, "aromatization mode set to \"%s\"\n", arom);
   DINGO_END;
}


CEXPORT int dingoSetLabelMode (const char* mode)
{
   DINGO_BEGIN;
   TL_GET(RenderParams, _params);
   if (strcasecmp(mode, "normal") == 0)
      _params.rOpt.labelMode = LABEL_MODE_NORMAL;
   else if (strcasecmp(mode, "forceshow") == 0)
      _params.rOpt.labelMode = LABEL_MODE_FORCESHOW;
   else if (strcasecmp(mode, "hideterminal") == 0)
      _params.rOpt.labelMode = LABEL_MODE_HIDETERMINAL;
   else if (strcasecmp(mode, "forcehide") == 0)
      _params.rOpt.labelMode = LABEL_MODE_FORCEHIDE;
   else
      throw Error("label mode unrecognized: \"%s\"", mode);
   dingoLogPrintf(_log, "label mode set to \"%s\"\n", mode);
   DINGO_END;
}

CEXPORT int dingoSetAAMColor (float r, float g, float b)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "aam color set to (%f, %f, %f)\n", r, g, b);
   TL_GET(RenderParams, _params);
   CHECKRGB(r, g, b);
   _params.rcOpt.aamColor.set(r, g, b);
   DINGO_END;
}

CEXPORT int dingoSetCenterDoubleBondWhenStereoAdjacent (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "double bond with adjacent stereo bond end centering set is %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.rOpt.centerDoubleBondWhenStereoAdjacent = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetQueryMode (int enabled)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "query mode to %s\n", enabledToString(enabled));
   TL_GET(RenderParams, _params);
   _params.query = (bool)enabled;
   DINGO_END;
}

CEXPORT int dingoSetComment (const char* comment)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "comment: %s\n", comment);
   TL_GET(RenderParams, _params);
   _params.rOpt.comment.clear();
   _params.rOpt.comment.appendString(comment, true);
   DINGO_END;
}

CEXPORT int dingoSetCommentFontSize (float fontSize)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "comment font size: %f\n", fontSize);
   TL_GET(RenderParams, _params);
   _params.rcOpt.commentFontFactor = fontSize;
   DINGO_END;
}

CEXPORT int dingoSetCommentPosition (const char* pos)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "comment position: %s\n", pos);
   TL_GET(RenderParams, _params);
   if (strcasecmp(pos, "top") == 0)
      _params.rOpt.commentPos = COMMENT_POS_TOP;
   else if (strcasecmp(pos, "bottom") == 0)
      _params.rOpt.commentPos = COMMENT_POS_BOTTOM;
   else
      throw Error("comment position unrecognized: \"%s\"", pos);
   DINGO_END;
}

CEXPORT int dingoSetCommentAlignment (const char* align)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "comment alignment size: %s\n", align);
   TL_GET(RenderParams, _params);
   if (strcasecmp(align, "left") == 0)
      _params.rOpt.commentAlign = ALIGNMENT_LEFT;
   else if (strcasecmp(align, "center") == 0)
      _params.rOpt.commentAlign = ALIGNMENT_CENTER;
   else if (strcasecmp(align, "right") == 0)
      _params.rOpt.commentAlign = ALIGNMENT_RIGHT;
   else
      throw Error("comment alignment unrecognized: \"%s\"", align);
   DINGO_END;
}

CEXPORT int dingoSetCommentColor (float r, float g, float b)
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "comment color set to (%f, %f, %f)\n", r, g, b);
   TL_GET(RenderParams, _params);
   CHECKRGB(r, g, b);
   _params.rOpt.commentColor.set(r, g, b);
   DINGO_END;
}

CEXPORT int dingoRender ()
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "rendering...\n");
   TL_GET(RenderParams, _params);
   TL_GET(Array<char>, _outbuf);
   TL_GET(Obj<FileOutput>, _outfileobj);
   TL_GET(Obj<ArrayOutput>, _outbufobj);
   _outbuf.clear();

   if (_params.outfile.size() > 0)
      _params.output = &_outfileobj.create(_params.outfile.ptr());
   else
      _params.output = &_outbufobj.create(_outbuf);

   RenderParamInterface::render(_params);

   disposeOutput();
   DINGO_END;
}

CEXPORT int dingoReset ()
{
   DINGO_BEGIN;
   dingoLogPrintf(_log, "reset state!\n");
   TL_GET(RenderParams, _params);
   _params.clear();
   DINGO_END;
}

CEXPORT int dingoGetResult (byte **buf, int *buf_size)
{
   DINGO_BEGIN;

   dingoLogPrintf(_log, "get result\n");
   TL_GET(Array<char>, _outbuf);
   *buf = (byte*)_outbuf.ptr();
   *buf_size = _outbuf.size();
   CHECKLOG { _log->writeArray(_outbuf); _log->flush(); }
   DINGO_END;
}

CEXPORT int dingoLayout()
{
   DINGO_BEGIN;

   TL_GET(RenderParams, _params);
   TL_GET(Array<char>, _outbuf);
   TL_GET(Obj<FileOutput>, _outfileobj);
   TL_GET(Obj<ArrayOutput>, _outbufobj);
   _outbuf.clear();

   if (_params.outfile.size() > 0)
      _params.output = &_outfileobj.create(_params.outfile.ptr());
   else
      _params.output = &_outbufobj.create(_outbuf);

   RenderParamInterface::layout(_params);

   disposeOutput();
   DINGO_END;
}
